﻿package  {
	/**
	* ...
	* @author Exey Panteleev
	* Math by Yamasv http://yamasv.blog92.fc2.com/blog-entry-85.html
	*/
	import flash.utils.getDefinitionByName;
	import flash.display.StageAlign;
	import flash.display.StageQuality;
	import flash.display.BitmapData;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import net.hires.utils.Stats;
	import org.papervision3d.view.BasicView;
	import org.papervision3d.core.math.Number3D;
    import org.papervision3d.core.geom.renderables.Vertex3D;
	import org.papervision3d.core.geom.renderables.Triangle3D;
	import org.papervision3d.core.proto.MaterialObject3D;
	import org.papervision3d.materials.BitmapAssetMaterial;
	import org.papervision3d.materials.BitmapMaterial;
	import org.papervision3d.materials.WireframeMaterial;
	import org.papervision3d.materials.ColorMaterial;
	import org.papervision3d.materials.special.CompositeMaterial;
	import org.papervision3d.materials.shaders.EnvMapShader;
	import org.papervision3d.materials.shaders.ShadedMaterial;
	import org.papervision3d.materials.shaders.Shader;
	import org.papervision3d.objects.primitives.Plane;
	import org.papervision3d.lights.PointLight3D;
	
	[SWF(backgroundColor="#222015", frameRate="60", width="600", height="400")]
	public class Fluid2 extends BasicView {
			//Math
            private var theta:Number = 0;
            private var buffer:Array = new Array(2);
            private var renderBuffer:int = 0;
            private var k1:Number;
            private var k2:Number;
            private var k3:Number;
			
			//Common
			private var range:int = 20; // play with tesselation
			private var waterSegsX:int = range;
			private var waterSegsY:int = range;
			private var waterSegsXreal:int = waterSegsX+1;
			private var waterSegsYreal:int = waterSegsY+1;
			private var waterXcenter:int = range/2;
			private var waterYcenter:int= range/2;
			private var isPause:Boolean = false;

			//3D
			private var waterSurface:Plane;
			private var light:PointLight3D;
			
		public function Fluid2() {
			super(600, 400, false, false, "CAMERA3D");
			preparePv3d();
			prepareMath();
			createObjects();
			addListeners();
		}
		public function preparePv3d():void {
			stage.quality   = "LOW";
			stage.scaleMode = "noScale";
			stage.align = StageAlign.TOP_LEFT;
			camera.zoom = 5;
			addChild(new Stats()) // MrDoob Stats http://code.google.com/p/mrdoob/
		}
		
		public function changeWaterMaterial(matIndex:uint):void {
			var material1:CompositeMaterial;
			var material2:ShadedMaterial;
			var envmapAsset:Class = getDefinitionByName("pat710.jpg") as Class; //"BlueSkies" by Michael Lambert http://www.filterforge.com/filters/710.html
			var envmapBitmapData:BitmapData = new envmapAsset(0, 0) as BitmapData; 
			var waterShader:EnvMapShader;
			var waterBitmapMat:BitmapMaterial;
			var bumpmapAsset:Class;
			var bumpmapBitmapData:BitmapData;
			
			switch(matIndex) {
				case 1:
					material1 = new CompositeMaterial();
					material1.addMaterial(new ColorMaterial(0x0000fff, 1));
					var wireframe:WireframeMaterial = new WireframeMaterial(0x9b85f9, 1);
					wireframe.doubleSided = true;
					material1.addMaterial(wireframe);
					waterSurface.material = material1;
					makeSplash(30); // play with splash height
				break;
				case 2:
					bumpmapAsset = getDefinitionByName("bump465.jpg") as Class; 
					bumpmapBitmapData = new bumpmapAsset(0,0) as BitmapData;
					waterShader = new EnvMapShader(light, envmapBitmapData, envmapBitmapData,0,bumpmapBitmapData);
					waterBitmapMat = new BitmapAssetMaterial("diffuse465.jpg"); //"poolwater" by DrEvil http://www.filterforge.com/filters/465.html
					material2 = new ShadedMaterial(waterBitmapMat, waterShader);
					waterSurface.material = material2;
					makeSplash(30);
				break;
				case 3:
					bumpmapAsset = getDefinitionByName("bump465.jpg") as Class;
					bumpmapBitmapData = new bumpmapAsset(0,0) as BitmapData;
					waterShader = new EnvMapShader(light, envmapBitmapData, envmapBitmapData,0,bumpmapBitmapData);
					waterBitmapMat = new BitmapAssetMaterial("pat2192.jpg"); //"Aqua Absolute" by Zephos http://www.filterforge.com/filters/2192.html
					material2 = new ShadedMaterial(waterBitmapMat, waterShader);
					waterSurface.material = material2;
					makeSplash(30);
				break;
				case 4:
					bumpmapAsset = getDefinitionByName("bump4141.jpg") as Class;
					bumpmapBitmapData = new bumpmapAsset(0,0) as BitmapData; 
					waterShader = new EnvMapShader(light, envmapBitmapData, envmapBitmapData,0,bumpmapBitmapData);
					waterBitmapMat = new BitmapAssetMaterial("diffuse4141.jpg"); // "Sea Water" by Constantin Malkov http://www.filterforge.com/filters/4141.html
					material2 = new ShadedMaterial(waterBitmapMat, waterShader);
					waterSurface.material = material2;
					makeSplash(30);
				break;
			}
			waterSurface.material.doubleSided = true;
		}
		public function createObjects():void {
			light = new PointLight3D(false, false);
			waterSurface = new Plane(new MaterialObject3D(), 1200, 1200, waterSegsX, waterSegsY);
			changeWaterMaterial(1);
			waterSurface.material.doubleSided = true;
			waterSurface.rotationX = -90;
			scene.addChild(waterSurface);
			camera.lookAt(waterSurface);
			trace("vertices: ", waterSurface.geometry.vertices.length, "faces: ", waterSurface.geometry.faces.length)
		}

		public function prepareMath():void {
			populateBuffer();
			//trace(buffer[0].length, buffer[1].length)
			var d:Number = 1;
			var t:Number = 0.1;
			var c:Number = 2.1;
			var mu:Number = 0.001;

			var f1:Number = c*c*t*t/(d*d);
			var f2:Number = 1/(mu*t+2);

			k1 = (4 - 8*f1)*f2;
			k2 = (mu*t-2)*f2;
			k3 = 2 * f1 * f2;
		}
		
		public function populateBuffer():void {
			var x:int,y:int,z:int;
			// 3-d displacement will be stored in the array(2*w*h)
			for(var i:int = 0 ; i < 2; i++){
				var a:Array = new Array();
				for(var j:int = 0 ; j < waterSegsYreal; j++){
					var aa:Array = new Array();
					for(var k:int = 0 ; k < waterSegsXreal; k++)
						aa.push(new Number3D(j,0,k));
					a.push(aa);
				}
				buffer[i] = a;
			}
		}
		
		public function makeSplash(splashHeight:int):void {
			populateBuffer();
			buffer[renderBuffer][waterXcenter][waterYcenter].y = splashHeight;	
		}
		
		public function addListeners():void {
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
		}

		private function evaluate():void{
			for(var j:int = 1 ; j < waterSegsYreal-1; j++){
				var crnt:Array = buffer[renderBuffer][j];
				var prev:Array = buffer[1-renderBuffer][j];
				for (var i:int = 1 ; i < waterSegsXreal-1; i++) {
				//	trace(j, i);
					var currentN:Number3D = (Number3D)(buffer[renderBuffer][j + 1][i]);
					var currentP:Number3D = (Number3D)(buffer[renderBuffer][j - 1][i]);
					((Number3D)(prev[i])).y = 
						k1*((Number3D)(crnt[i])).y + k2*((Number3D)(prev[i])).y + 
						k3*(((Number3D)(crnt[i+1])).y + ((Number3D)(crnt[i-1])).y
								+ currentN.y + currentP.y);
				}
			}
			renderBuffer = 1-renderBuffer;
		}

		public function onEnterFrame(event:Event):void {
			light.copyPosition(camera);
			if(!isPause){
                theta += 0.05;
				var verCounter:uint = 0;
                for(var x:int = 0 ; x < waterSegsXreal ; x++){
                    for(var z:int = 0 ; z < waterSegsYreal ; z++){
 						waterSurface.geometry.vertices[verCounter].z = buffer[renderBuffer][x][z].y * 3;
						verCounter++;
                    }
                }
				evaluate();
				for each(var face:Triangle3D in waterSurface.geometry.faces)
					face.createNormal();
				
				for each(var vertex:Vertex3D in waterSurface.geometry.vertices)
					vertex.calculateNormal();	
			}
			rotateCamera();
            singleRender();
		}
		
		private function rotateCamera():void{
			var x:Number = (stage.mouseX - stage.stageWidth/2) / 4;
			camera.moveLeft(x);
			var y:Number = (stage.mouseY - stage.stageHeight/2) / 6;
			camera.moveDown(y);
			if (camera.y > 1000 || camera.y < -200) camera.moveDown(-y);
		}
		public function keyUpHandler(e:KeyboardEvent):void {
			if (isChar(e.charCode, "1")) 
				changeWaterMaterial(1);
			else if(isChar(e.charCode, "2"))
				changeWaterMaterial(2);
			else if (isChar(e.charCode, "3")) 
				changeWaterMaterial(3);
			else if (isChar(e.charCode, "4")) 
				changeWaterMaterial(4);
			else if (isChar(e.charCode, "0")) 
				if(isPause == false)
					isPause = true;
				else
					isPause = false;
		}
		
		private function isChar(code:Number, str:String):Boolean{
			if(code == str.charCodeAt())
				return true;
			return false;
		}
	}
}